home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 January / macformat-020.iso / Shareware City / Developers / apps.to.go / AppsToGo.docs / =How to write your app < prev   
Encoding:
Text File  |  1993-04-12  |  39.8 KB  |  967 lines  |  [TEXT/MPS ]

  1. The title of this document should have been:
  2.  
  3. How to write your application using AppWannabe (or Wannabe for short).
  4.  
  5. The above is too long for a file name, but obviously the title I used got your
  6. attention.  Now all I have to do is to hold it...
  7.  
  8. If you have built the libraries and sample applications, you may have already
  9. noted that Wannabe is actually an executable application.  Of course, it does
  10. absolutely nothing useful.  That's your job.
  11.  
  12. I am assuming that you have built the libraries and Wannabe.
  13.  
  14. If you use MPW, you built the libraries DTS.Lib_controls, DTS.Lib_ctlhandler,
  15. DTS.Lib_framework, DTS.Lib_strings, DTS.Lib_treeobj, DTS.Lib_utils.
  16.  
  17. If you use Think, you built the libraries DTS.Lib..controls, DTS.Lib..ctlhandler,
  18. DTS.Lib..framework, DTS.Lib..strings, DTS.Lib..treeobj, DTS.Lib..utils.
  19.  
  20. DTS.Lib_strings (or DTS.Lib..strings) contains only a single source file,
  21. StringUtils.c.  StringUtils.c contains some useful string functions.  It allows
  22. you to do various functions that are supplied by sprintf.  The amount of code
  23. you have to link in when you choose sprintf is quite large, however.  The
  24. supplied string functions allow you to easily format and parse string data
  25. without linking a large chunk of code.
  26.  
  27. It is suggested that you link the StringUtils executable into the code segment
  28. that contains main().  This will guarantee that the string functions are always
  29. resident in memory when you call them.  (MPW make files and Think projects are
  30. already set up this way.)  If the code isn't in ram when you make a string
  31. call, the loading of the code may cause memory to move or compress.  This
  32. is fine, unless you passed a pointer into an unlocked handle as a string
  33. pointer.  If you point into an unlocked handle, simply calling the function can
  34. move memory, unless it is guaranteed that the code is already in memory.  By
  35. linking it into the same code segment as main(), you guarantee that it is
  36. always in memory.  (All of the sample applications already do this.)
  37.  
  38.  
  39. Various libraries can be used without committing to the entire DTS.Lib application
  40. framework.  You can write your own application completely from scratch and then
  41. call any of the code in the above-mentioned files anytime you want.
  42.  
  43. Each of the above files has useful stand-alone code you will probably want to
  44. look at, even if you choose not to use the DTS.Lib application framework.
  45.  
  46. If you choose to use the DTS.Lib application framework, (from now on called
  47. DTS.framework), very much of the application development tasks are already done
  48. for you.
  49.  
  50. DTS.framework currently supports:
  51.     Multiple windows
  52.     Multiple document types
  53.     File I/O
  54.     Apple Events
  55.     Window/document scrolling
  56.     Hierarchical document architecture
  57.     Infinite undo (undos can optionally be saved with the document)
  58.     Floating palettes
  59.     Viewing window for displaying document objects
  60.     Other document-specific tasks, such as mouseDowns, keyDowns, cursors, etc.
  61.  
  62. What tasks DTS.framework doesn't directly supply can probably be found in the
  63. DTS utilities, so between the two, many of the aspects of Mac application
  64. development are already complete.
  65.  
  66.  
  67. So what is the minimum you have to know to get started?
  68.  
  69. Above, we discussed the various components of the DTS library code.  So now we
  70. need to know the easiest way to use this code.  The easiest way is to make a
  71. copy of the sample application Wannabe, and start adding code.  Consider Wannabe
  72. an application shell which already correctly uses the DTS.framework and
  73. utilities.  All that is missing is the code that is specific to your
  74. application.  Everything that is generic is already in there.
  75.  
  76. The best place to start explaining is how to manage documents.  We will start
  77. with a code snippet from Wannabe:
  78.  
  79. switch (menuItem) {
  80.     case kStdNew:
  81.         err = NewDocument(&frHndl, gAppWindowType, true);
  82.         if (!err) {
  83.             err = DoNewWindow(frHndl, nil, FrontWindow(), (WindowPtr)-1);
  84.             if (err)
  85.                 DisposeDocument(frHndl);
  86.         }
  87.         if (err)
  88.             CenteredAlert(rErrorAlert, nil, (ModalFilterProcPtr)AlertFilter);
  89.         break;
  90.         .
  91.         .
  92.         .
  93.  
  94.  
  95. This is right out of the file Menu.c  We get to this code by the user choosing
  96. "New" from the file menu.  The first thing we do is to try to create the new
  97. document.  This is done with the DTS.framework call NewDocument().
  98.  
  99. All NewDocument() does, if successful, is to create a handle to the size of the
  100. document structure, and initialize many of the fields.  If it succeeds, frHndl
  101. is set to that handle.  If it fails, frHndl is set to nil, and the reason for
  102. the failure is returned into err.
  103.  
  104. As stated above, DTS.framework can support multiple document types.  The type
  105. of the document we are trying to create is docFileType, which is an OSType.
  106. In Wannabe, docFileType is defined as follows:
  107.     #define docFileType      'DUMD'
  108.  
  109. 'DUMD' stands for DUMb Document.  I consider it dumb because when the document
  110. is saved, it is saved with no content.  The DTS.framework very carefully saves
  111. nothing.  You can then open nothing later.  You can print nothing, etc.
  112. Hopefully, you will remedy this situation.
  113.  
  114. The third parameter to NewDocument() tells NewDocument() that you want to
  115. create a document with a new "Untitled #" style of window title.  The true
  116. indicates to DTS.framework that you want this document to have an untitled
  117. number one greater then the last time that NewDocument() was called.  No big
  118. deal, but I figured you would be curious at this point.
  119.  
  120. If NewDocument() succeeds, you now have a new document that has no window.
  121. Once the document is successfully created, you then want to give the document
  122. a window.  This is done with the DoNewWindow() call.  You pass in the frHndl
  123. that NewDocument() created.
  124.  
  125. One of the fields in the frHndl that is initialized is the fileState.attributes
  126. field.  This field describes various window attributes if a window is opened for
  127. this document.  The default attributes is kept in the global gAppWindowAttr.
  128. If you don't like these attributes, you can change them in the frHndl after it
  129. initialized and before you give the document a window.
  130.  
  131. The various values for the attribute field are:
  132.  
  133.     #define kwGrowIcon            0x00000001L
  134.     #define kwHScroll            0x00000002L
  135.     #define kwHScrollLessGrow    0x00000006L
  136.     #define kwVScroll            0x00000008L
  137.     #define kwVScrollLessGrow    0x00000018L
  138.     #define kwVisible            0x00000020L
  139.     #define kwOpenAtOldLoc        0x00000040L
  140.     #define kwDoFirstClick        0x00000080L
  141.     #define kwHideOnClose        0x00000100L
  142.     #define kwIsDocument        0
  143.     #define kwIsPalette            0x00000200L
  144.     #define kwIsModalDialog        0x00000400L
  145.     #define kwDefaultDocHeader    0x00000800L
  146.     #define kwHeaderIsResource    0x00001000L
  147.     #define kwRuntimeOnlyDoc    0x00002000L
  148.     #define kwAutoNew            0x00004000L
  149.     #define kwDefaultDocType    0x00008000L
  150.     #define kwColorMonitor        0x00010000L
  151.     #define kwSecondaryMonitor    0x00020000L
  152.     #define kwStaggerWindow        0x00040000L
  153.     #define kwCenterWindow        0x00080000L
  154.     #define kwSameMonitor        0x00100000L
  155.  
  156. So, if you want to create a window with document scrollbars, you just set a few
  157. bits in the attribute field, and you're done.  And yes, kwOpenAtOldLoc
  158. automatically opens the window wherever the user had it when it was closed,
  159. and yes it makes sure that it is at least partially visible on some monitor.
  160. You can find more information elsewhere.
  161.  
  162. In the Wannabe file File.c, you get called after the defaults are placed in the frHndl.
  163. The function InitDocument() is called.  You can check the OSType of the frHndl, and
  164. then make adjustments based on the various document types in your application.
  165. To dereference to the attributes field, given an frHndl, do like so:
  166.     (*frHndl)->fileState.attributes = blah;
  167.  
  168. Other changes can be made to the frHndl at this point.  This is where you state that you
  169. don't want certain default behaviors for the document.  (More on this later.)
  170.  
  171.  
  172. Assuming that the frHndl got created successfully, you then probably want to give the document
  173. a window.  Going back to our code:
  174.  
  175.         err = NewDocument(&frHndl, gAppWindowType, true);
  176.         if (!err) {
  177.             err = DoNewWindow(frHndl, nil, FrontWindow(), (WindowPtr)-1);
  178.             if (err)
  179.                 DisposeDocument(frHndl);
  180.         }
  181.         if (err)
  182.             CenteredAlert(rErrorAlert, nil, (ModalFilterProcPtr)AlertFilter);
  183.         break;
  184.  
  185. We call DoNewWindow(), and we pass it the successfully created (and possibly modified)
  186. frHndl.  DoNewWindow() looks up various fields in the frHndl, and creates the window
  187. accordingly.
  188.  
  189.  
  190. The prototype for DoNewWindow is:
  191.  
  192. OSErr    DoNewWindow(FileRecHndl frHndl, WindowPtr *retWindow,
  193.                     WindowPtr relatedWindow, WindowPtr behind);
  194.  
  195. If DoNewWindow() successfully creates a window, the window is returned in
  196. retWindow, if retWindow is not nil.  Many times you don't really care what the
  197. window that was created is.  In this case, if it is successful, it is now the
  198. new top window.  If the user clicks on it, then you care.  Here you don't.
  199.  
  200. If DoNewWindow() was unsuccessful at creating the window, you now are left with
  201. a document, frHndl, that has no window.  If this occurs, you want to dispose of
  202. the document, and report an error.
  203.  
  204. That's all there is to it.  Oh, the remaining parameters to DoNewWindow():
  205.     relatedWindow:    Create the window on the same monitor that holds most of
  206.                     this window. If nil is passed in, create the window on the
  207.                     main monitor.
  208.     behind:            Create the window behind the window indicated.
  209.                     -1 puts it on top.
  210.  
  211.  
  212. Now that we understand document creation and window creation using
  213. DTS.framework, here's the rest of the story:
  214.  
  215.  
  216. As mentioned above, NewDocument() returns a handle.  This handle is your
  217. reference to the document, thus my favorite variable name for one os these
  218. things:  frHndl (file reference handle).  You don't have to worry about
  219. associating the frHndl with the window that was created for it.  DoNewWindow()
  220. stores the frHndl in the window's refcon field.  Also, you don't have to worry
  221. about which window belongs to a particular frHndl, as the window is stored in
  222. the frHndl, as well.  The document and its associated window both point to one
  223. another.  (You got one, you got the other -- cool.)
  224.  
  225.  
  226. Basically, everything that goes on with documents goes on inside the frHndl.
  227. The frHndl is a handle that holds the structure for the document.  Most of this
  228. structure looks like this:
  229.  
  230. typedef struct {
  231.     OSType                        sfType;
  232.     Boolean                        defaultDoc;
  233.     Movie                        movie;
  234.     short                        movieResID;
  235.     short                        movieFlags;
  236.     Boolean                        movieDataRefWasChanged;
  237.     Boolean                        docDirty;
  238.     long                        modNum;
  239.     long                        modTick;
  240.     Boolean                        readOnly;
  241.     short                        refNum;
  242.     short                        resRefNum;
  243.     FSSpec                        fss;
  244.     short                        windowID;
  245.     WindowPtr                    window;
  246.     PositionWndProcPtr            getDocWindow;
  247.     CalcFrameRgnProcPtr            calcFrameRgnProc;
  248.     ContentClickProcPtr            contentClickProc;
  249.     ContentKeyProcPtr            contentKeyProc;
  250.     DrawFrameProcPtr            drawFrameProc;
  251.     FreeDocumentProcPtr            freeDocumentProc;
  252.     FreeWindowProcPtr            freeWindowProc;
  253.     ImageProcPtr                imageProc;
  254.     InitContentProcPtr            initContentProc;
  255.     ReadDocumentProcPtr            readDocumentProc;
  256.     ReadDocumentHeaderProcPtr    readDocumentHeaderProc;
  257.     ResizeContentProcPtr        resizeContentProc;
  258.     ScrollFrameProcPtr            scrollFrameProc;
  259.     UndoFixupProcPtr            undoFixupProc;
  260.     WindowCursorProcPtr            windowCursorProc;
  261.     WriteDocumentProcPtr        writeDocumentProc;
  262.     WriteDocumentHeaderProcPtr    writeDocumentHeaderProc;
  263.     AdjustMenuItemsProcPtr        adjustMenuItemsProc;
  264.     DoMenuItemProcPtr            doMenuItemProc;
  265.     long                        attributes;            /* Here down is window content information. */
  266.     Rect                        windowSizeBounds;
  267.     ControlHandle                hScroll;
  268.     ControlHandle                vScroll;
  269.     short                        hScrollIndent;
  270.     short                        vScrollIndent;
  271.     short                        leftSidebar;
  272.     short                        topSidebar;
  273.     short                        hArrowVal;
  274.     short                        vArrowVal;
  275.     short                        hPageVal;
  276.     short                        vPageVal;
  277. } FileStateRec;
  278.  
  279.  
  280. Here's some explanation of some of the fields:
  281.  
  282. sfType:                    The type of the document you passed to NewDocument() is
  283.                         stored here.
  284.  
  285. defaultDoc:                Used by application framework to determine if View Hierarchy
  286.                         facility can be used to view the document.
  287.  
  288. movie:                    These 4 fields are used together.  If the document type used
  289. movieResID:                is of type 'MooV' for either NewDocument or OpenDocument,
  290. movieFlags:                then these fields are used to keep track of the movie information
  291. movieDataRefWasChanged:    for the document.  In addition to this information, the meaning of
  292.                         the field refNum is extended somewhat.  Here's the deal:
  293.                         Movie files are generally resource-fork-based, but this isn't
  294.                         always the case.  The movie file can be flattened and made to
  295.                         be data-fork-based.  This is so MS-DOS machines can handle the
  296.                         movie file.
  297.                         To be consistent with regular document files, the resRefNum
  298.                         returned by OpenMovieFile (which isn't always a resource refNum)
  299.                         is kept in the field refNum.  Normally refNum represents a data
  300.                         fork, but in the case of movies, it may be either a data fork or
  301.                         resource fork reference number.
  302.                         For regular documents, if you wish to use the resource fork, you
  303.                         use the calls UseDocResFile and CloseDocResFile.  These still work
  304.                         for movies, whether the movie is data-fork- or resource-fork-based.
  305.                         If the movie is resource fork based, then UseDocResFile simply
  306.                         copies the refNum field into the resRefNum field.  This is the
  307.                         correct thing to do, as the resource is already opened for the movie.
  308.                         CloseDocResFile looks at the refNum and resRefNum fields.  If they
  309.                         are the same, then it simply sets resRefNum to kInvalidRefNum.
  310.                         This is all that is necessary to "close" the resource fork, as you
  311.                         really don't want it to close, since it was opened for the movie.
  312.                         All of this behavior is simply to allow the various calls to do the
  313.                         appropriate thing when the document is a movie.  You should be able
  314.                         to handle movie files just like regular document files.
  315.  
  316. docDirty:                Used by the application framework to determine if a
  317.                         "Save before closing" dialog should be displayed.  
  318.  
  319. modNum:                    This is incremented whenever you call SetDocDirty().  Some
  320.                         applications find this information useful.  Most don't care.
  321.  
  322. modTick:                Tick count of last modification.  (See modNum).
  323.  
  324. readOnly:                True if document is opened read-only.  DTS.framework sets
  325.                         this if the document that is selected by the user can't be
  326.                         openeded with read-write access.  DTS.framework asks the
  327.                         user if it is okay to open it read-only.
  328.  
  329. refNum:                    The file reference number (0 if it is a new document.)
  330.  
  331. resRefNum:                Reference number of the resource fork of the document file.
  332.                         Unless you specifically use the resource fork, the
  333.                         document's resource fork isn't opened.
  334.  
  335. fss                        The FSSpec for the open file (if there is one open for this
  336.                         document.)
  337.  
  338. windowID:                The resource ID of the 'WIND' resource to be used when
  339.                         DoNewDocument() is called to give this document a window.
  340.                         windowID is initialized to rWindow, which is defined to be
  341.                         128.  If you want a different 'WIND' resource for this
  342.                         document, set this field after calling NewDocument(), and
  343.                         prior to calling DoNewWindow().
  344.  
  345. window:                    When you call DoNewWindow(), if it succeeds at creating a
  346.                         window, it places the window pointer here.
  347.  
  348. The next fields are the procedure pointers that determine the behavior of the
  349. document.  These fields are initialized to point to the following functions:
  350.  
  351. For documents:
  352.  
  353.     getDocWindow:                GetStaggeredWindow
  354.     calcFrameRgnProc:            CalcFrameRgn
  355.     contentClickProc:            ContentClick
  356.     contentKeyProc:                ContentKey
  357.     drawFrameProc:                DrawFrame
  358.     freeDocumentProc:            FreeDocument
  359.     freeWindowProc:                FreeWindow
  360.     imageProc:                    ImageDocument
  361.     initContentProc:            InitContent
  362.     readDocumentProc:            ReadDocument
  363.     readDocumentHeaderProc:        nil
  364.     resizeContentProc:            ResizeContent
  365.     scrollFrameProc:            ScrollFrame
  366.     undoFixupProc:                UndoFixup
  367.     windowCursorProc:            WindowCursor
  368.     writeDocumentProc:            WriteDocument
  369.     writeDocumentHeaderProc:    nil
  370.     adjustMenuItemsProc:        AdjustMenuItems
  371.     doMenuItemProc:                DoMenuItem
  372.  
  373.  
  374. For dialogs:
  375.  
  376.     getDocWindow:                GetStaggeredWindow
  377.     calcFrameRgnProc:            DialogCalcFrameRgn
  378.     contentClickProc:            DialogContentClick
  379.     contentKeyProc:                DialogContentKey
  380.     drawFrameProc:                DialogDrawFrame
  381.     freeDocumentProc:            DialogFreeDocument
  382.     freeWindowProc:                DialogFreeWindow
  383.     imageProc:                    DialogImageDocument
  384.     initContentProc:            DialogInitContent
  385.     readDocumentProc:            nil
  386.     readDocumentHeaderProc:        nil
  387.     resizeContentProc:            DialogResizeContent
  388.     scrollFrameProc:            DialogScrollFrame
  389.     undoFixupProc:                DialogUndoFixup
  390.     windowCursorProc:            DialogWindowCursor
  391.     writeDocumentProc:            nil
  392.     writeDocumentHeaderProc:    nil
  393.     adjustMenuItemsProc:        DialogAdjustMenuItems
  394.     doMenuItemProc:                DialogDoMenuItem
  395.  
  396.  
  397. For palettes:
  398.  
  399.     getDocWindow:                GetStaggeredWindow
  400.     calcFrameRgnProc:            PaletteCalcFrameRgn
  401.     contentClickProc:            PaletteContentClick
  402.     contentKeyProc:                PaletteContentKey
  403.     drawFrameProc:                PaletteDrawFrame
  404.     freeDocumentProc:            PaletteFreeDocument
  405.     freeWindowProc:                PaletteFreeWindow
  406.     imageProc:                    PaletteImageDocument
  407.     initContentProc:            PaletteInitContent
  408.     readDocumentProc:            nil
  409.     readDocumentHeaderProc:        nil
  410.     resizeContentProc:            PaletteResizeContent
  411.     scrollFrameProc:            PaletteScrollFrame
  412.     undoFixupProc:                PaletteUndoFixup
  413.     windowCursorProc:            PaletteWindowCursor
  414.     writeDocumentProc:            nil
  415.     writeDocumentHeaderProc:    nil
  416.     adjustMenuItemsProc:        AdjustMenuItems
  417.     doMenuItemProc:                DoMenuItem
  418.  
  419. Note that unless gAppWindowAttr is set for a dialog or palette document/window,
  420. all documents will be created with the first set of procPtrs.
  421.  
  422. (AppsToGo allows you to set a document/window as a dialog or palette, and if
  423. you are using AppsToGo for development, then gAppWindowAttr isn't used.  Instead
  424. the attributes for the document/window description set with AppsToGo are.
  425. AppsToGo allows you to indicate if a document/window should be a dialog or palette.)
  426.  
  427. With the exception of the function GetStaggeredWindow, all of the above
  428. functions are part of the application shell Wannabe.  GetStaggeredWindow is a
  429. function in the DTS utilities that opens a window "staggered" from existing
  430. windows.  This allows some of the window to be exposed so that the user can
  431. easily click on the window and bring it to the front.
  432.  
  433. All of the other functions are part of your application.  They get called by
  434. the application framework at appropriate times.  They don't get called at some
  435. magic moment.  They get called when your application calls DTS.framework to do
  436. something.  DTS.framework does the generic work. The application-specific work
  437. it can't do, and so it calls your application to do that part.
  438.  
  439. Even for something as supposedly application-specific as drawing the window
  440. content, you still will call DTS.framework to get the job started.  You will
  441. call the function DoImageDocument().  You pass DoImageDocument() a file
  442. reference handle (frHndl).  DoImageDocument() dereferences the frHndl and
  443. gets the value at the field imageProc.  If imageProc is nil, then there is
  444. no imaging procedure for this document, and DoImageDocument() just returns.
  445. If there is a procedure pointer stored in the field imageProc, then that
  446. code is called.
  447.  
  448. There is no particular magic happening here.  The DoImageDocument() code looks
  449. just as you would expect:
  450.  
  451. OSErr    DoImageDocument(FileRecHndl frHndl)
  452. {
  453.     ImageProcPtr    proc;
  454.     OSErr            err;
  455.  
  456.     err = noErr;
  457.     if (proc = (*frHndl)->fileState.imageProc) err = (*proc)(frHndl);
  458.     return(err);
  459. }
  460.  
  461. If there is an imaging procedure in the file reference, call it.  Otherwise
  462. it just returns noErr.
  463.  
  464. Initially imageProc has a value.  The initial value is ImageDocument.  The
  465. ImageDocument() function is located in the Wannabe file Window.c.  If your
  466. application only has a single document type, then just place your document
  467. imaging code inside the stub function ImageDocument().  It's that easy.
  468.  
  469. For the most part, all of the procedure pointers in the frHndl have a "Do"
  470. function.  For example:  If you want to draw the "frame" portion of a window,
  471. you wouldn't call DrawFrame().  You would call DoDrawFrame().  DoDrawFrame()
  472. will look up the procedure pointer, and if it is not nil, it will call it,
  473. just like DoImageDocument() does.
  474.  
  475.  
  476. So how exactly does a file reference handle get created?  Again, nothing
  477. magic, but there are a number of steps that need to be understood.
  478.  
  479.  
  480. The creation of an frHndl is invoked by the application.  The application
  481. calls either NewDocument() or OpenDocument(), discussed above.  OpenDocument()
  482. calls NewDocument(), so in either case, you are actually calling NewDocument().
  483.  
  484. I think the easiest way to explain what NewDocument() does is to show the code.
  485. The two key places are marked with •••.  The explanation follows.  Here's most of the code:
  486.  
  487.  
  488. OSErr    NewDocument(FileRecHndl *returnHndl, OSType sftype, Boolean incTitleNum)
  489. {
  490.     long                size;
  491.     FileRecHndl            frHndl;
  492.     FileRecPtr            frPtr;
  493.     Str255                untitled;
  494.     StringPtr            pstr;
  495.     OSErr                err;
  496.     short                i;
  497.     Movie                movie;
  498.     TreeObjHndl            wobj;
  499.     PositionWndProcPtr    windowPlacementProc;
  500.     static short        untitledCount;
  501.  
  502.     if (returnHndl)
  503.         *returnHndl = nil;
  504.  
  505.     if (!sftype) {
  506.         if (!returnHndl)
  507.             untitledCount = 0;        /* API trick to allow you to reset the document count. */
  508.         return(noErr);
  509.     }
  510.  
  511.     err  = memFullErr;                /* Assume that we will fail. */
  512.  
  513. •••    size = InitDocumentSize(sftype);
  514.         /* Call the application and ask it how big the frHndl should be for
  515.         ** this document type.  We can't know, so we'll ask. */
  516.  
  517.     if (frHndl = (FileRecHndl)NewHandleClear(size)) {
  518.         /* Create (or try to) the frHndl, initialized to all 0's */
  519.  
  520.         if (returnHndl)
  521.             *returnHndl = frHndl;
  522.  
  523.         .
  524.         .
  525.         . This varies, depending on if you are using AppsToGo.
  526.         .
  527.         .
  528.  
  529. /* Below we fill in the various fields with defaults for the frHndl. */
  530.  
  531.         (*frHndl)->fileState.modNum = GetModNum();
  532.             /* In case GetModNum gets moved to another code segment, set this value
  533.             ** prior to dereferencing frHndl into frPtr. */
  534.  
  535.         frPtr = *frHndl;
  536.         frPtr->fileState.sfType                  = sftype;
  537.         frPtr->fileState.modTick                 = TickCount();
  538.         frPtr->fileState.refNum                  = kInvalRefNum;
  539.         frPtr->fileState.resRefNum               = kInvalRefNum;
  540.         frPtr->fileState.fss.vRefNum             = kInvalVRefNum;
  541.         frPtr->fileState.windowID                = rWindow;
  542.             /* The above sets the fileState constants for the document.  Note
  543.             ** that we use a default 'WIND' ID for the expected window resource.
  544.             ** This can be changed later, if the default isn't good enough. */
  545.  
  546.         if (wobj) {
  547.             frPtr->fileState.windowID      = mDerefWFMT(wobj)->windowID;
  548.             frPtr->fileState.attributes    = mDerefWFMT(wobj)->attributes;
  549.             frPtr->fileState.hScrollIndent = mDerefWFMT(wobj)->hScrollIndent;
  550.             frPtr->fileState.vScrollIndent = mDerefWFMT(wobj)->vScrollIndent;
  551.             frPtr->fileState.leftSidebar   = mDerefWFMT(wobj)->leftSidebar;
  552.             frPtr->fileState.topSidebar    = mDerefWFMT(wobj)->topSidebar;
  553.                 /* Set window attributes as described in resource. */
  554.         }
  555.         else
  556.             frPtr->fileState.attributes = gAppWindowAttr;
  557.                 /* Set window attributes for the main document type. If the document
  558.                 ** is not the main type, then the application's InitDocument function
  559.                 ** will have to change it. */
  560.  
  561.         frPtr->fileState.getDocWindow        = windowPlacementProc;
  562.         frPtr->fileState.adjustMenuItemsProc = AdjustMenuItems;
  563.         frPtr->fileState.doMenuItemProc      = DoMenuItem;
  564.         switch (frPtr->fileState.attributes & (kwIsPalette | kwIsModalDialog)) {
  565.             case kwIsPalette:
  566.                 frPtr->fileState.calcFrameRgnProc  = PaletteCalcFrameRgn;
  567.                 frPtr->fileState.contentClickProc  = PaletteContentClick;
  568.                 frPtr->fileState.contentKeyProc    = PaletteContentKey;
  569.                 frPtr->fileState.drawFrameProc     = PaletteDrawFrame;
  570.                 frPtr->fileState.freeDocumentProc  = PaletteFreeDocument;
  571.                 frPtr->fileState.freeWindowProc    = PaletteFreeWindow;
  572.                 frPtr->fileState.imageProc         = PaletteImageDocument;
  573.                 frPtr->fileState.initContentProc   = PaletteInitContent;
  574.                 frPtr->fileState.readDocumentProc  = nil;
  575.                 frPtr->fileState.resizeContentProc = PaletteResizeContent;
  576.                 frPtr->fileState.scrollFrameProc   = PaletteScrollFrame;
  577.                 frPtr->fileState.undoFixupProc     = PaletteUndoFixup;
  578.                 frPtr->fileState.windowCursorProc  = PaletteWindowCursor;
  579.                 frPtr->fileState.writeDocumentProc = nil;
  580.                 break;
  581.             case kwIsModalDialog:
  582.                 frPtr->fileState.calcFrameRgnProc    = DialogCalcFrameRgn;
  583.                 frPtr->fileState.contentClickProc    = DialogContentClick;
  584.                 frPtr->fileState.contentKeyProc      = DialogContentKey;
  585.                 frPtr->fileState.drawFrameProc       = DialogDrawFrame;
  586.                 frPtr->fileState.freeDocumentProc    = DialogFreeDocument;
  587.                 frPtr->fileState.freeWindowProc      = DialogFreeWindow;
  588.                 frPtr->fileState.imageProc           = DialogImageDocument;
  589.                 frPtr->fileState.initContentProc     = DialogInitContent;
  590.                 frPtr->fileState.readDocumentProc    = nil;
  591.                 frPtr->fileState.resizeContentProc   = DialogResizeContent;
  592.                 frPtr->fileState.scrollFrameProc     = DialogScrollFrame;
  593.                 frPtr->fileState.undoFixupProc       = DialogUndoFixup;
  594.                 frPtr->fileState.windowCursorProc    = DialogWindowCursor;
  595.                 frPtr->fileState.writeDocumentProc   = nil;
  596.                 frPtr->fileState.adjustMenuItemsProc = DialogAdjustMenuItems;
  597.                 frPtr->fileState.doMenuItemProc      = DialogDoMenuItem;
  598.                 break;
  599.             default:
  600.                 frPtr->fileState.calcFrameRgnProc  = CalcFrameRgn;
  601.                 frPtr->fileState.contentClickProc  = ContentClick;
  602.                 frPtr->fileState.contentKeyProc    = ContentKey;
  603.                 frPtr->fileState.drawFrameProc     = DrawFrame;
  604.                 frPtr->fileState.freeDocumentProc  = FreeDocument;
  605.                 frPtr->fileState.freeWindowProc    = FreeWindow;
  606.                 frPtr->fileState.imageProc         = ImageDocument;
  607.                 frPtr->fileState.initContentProc   = InitContent;
  608.                 frPtr->fileState.readDocumentProc  = ReadDocument;
  609.                 frPtr->fileState.resizeContentProc = ResizeContent;
  610.                 frPtr->fileState.scrollFrameProc   = ScrollFrame;
  611.                 frPtr->fileState.undoFixupProc     = UndoFixup;
  612.                 frPtr->fileState.windowCursorProc  = WindowCursor;
  613.                 frPtr->fileState.writeDocumentProc = WriteDocument;
  614.                 break;
  615.         }
  616.  
  617.         frPtr->fileState.windowSizeBounds.left   = kMinWindowWidth;
  618.         frPtr->fileState.windowSizeBounds.top    = kMinWindowHeight;
  619.         frPtr->fileState.windowSizeBounds.right  = kMaxWindowWidth;
  620.         frPtr->fileState.windowSizeBounds.bottom = kMaxWindowHeight;
  621.             /* Default min/max window size for growIcon. */
  622.  
  623.         pstr = frPtr->fileState.fss.name;
  624.         pcpy(pstr, untitled);
  625.         if (pstr[0]) {
  626.             if (incTitleNum)
  627.                 ++untitledCount;
  628.             pcatdec(pstr, untitledCount);
  629.                 /* Create the default document title.  It is stored in the FSSpec,
  630.                 ** as we can't place it in the window title.  We don't have a window
  631.                 ** yet to "title".  This will happen later.  The title is gotten from
  632.                 ** the FSSpec, so we are effectively done. */
  633.         }
  634.  
  635. •••        err = InitDocument(frHndl);
  636.             /* Call the application for any additional document initialization.
  637.             ** Other handles may need to be allocated.  The default values above
  638.             ** may be incorrect for certain document types.  This gives the
  639.             ** application a chance to change any defaults that are incorrect. */
  640.  
  641.         if ((*frHndl)->fileState.sfType == MovieFileType) {
  642.             movie = NewMovie(newMovieActive);
  643.             err   = GetMoviesError();
  644.             if (!err) {
  645.                 ClearMovieChanged(movie);
  646.                 (*frHndl)->fileState.movie = movie;
  647.             }
  648.         }
  649.  
  650.         if (err) {
  651.             DisposeHandle((Handle)frHndl);
  652.             if (returnHndl)
  653.                 *returnHndl = nil;
  654.                     /* If the application couldn't complete the document
  655.                     ** initialization, pitch the handle. */
  656.         }
  657.     }
  658.  
  659.     return(err);
  660. }
  661.  
  662.  
  663. Note the two lines marked with •••.  These two points the framework function NewDocument()
  664. calls the application, which are:
  665.  
  666. 1) The call to InitDocumentSize()
  667. 2) The call to InitDocument()
  668.  
  669.  
  670. These two functions are part of your application.  (They are in the source file
  671. File.c)  The first is called just so NewDocument() knows how big a handle to
  672. create for the document reference.  If your application has only one document
  673. type, then this function will simply return a constant.  If your application
  674. has multiple document types, you will want to return the size that corresponds
  675. to the document type (OSType).
  676.  
  677. Your application's function InitDocumentSize() is passed the OSType.  At this
  678. point, the framework has only piece of information on the document-to-be.
  679. Until the application framework knows the size, it can't create the file
  680. reference handle.  You tell the application framework how big to create the
  681. frHndl.  It creates it to the requested size, and initializes it to 0's and
  682. default values.
  683.  
  684. Once it is initialized to defaults, NewDocument() calls your application, giving
  685. it a chance to change the default values.  Only the frHndl is passed to
  686. InitDocument().  The frHndl from this point on is the document reference.
  687.  
  688. If you have different document types, you will have to have a reasonably smart
  689. InitDocument() function.  It will have to look at the document type, and then
  690. adjust the default values for the frHndl based on the document type.  At this
  691. point, the document type is stored in the frHndl, in the field fileState.sfType.
  692.  
  693. All of this is done prior to a window being created for the frHndl.  This is
  694. important, as a number of the procedure pointers determine how the window will
  695. be created.  You need to make customizations to the frHndl after its creation,
  696. and prior to the creation of the document's window.
  697.  
  698. Of course, if your application only has one document type, then these defaults
  699. are just fine, and you won't have to worry about all of this stuff.
  700.  
  701. The NewDocument() is pretty big, but that means that you don't have to worry about this
  702. stuff.  The NewDocument() function is in charge of creating a functioning frHndl.  The
  703. only thing your application has to do is to say how big it should be, and what defaults
  704. you didn't like.
  705.  
  706.  
  707. Let's revisit the code for a "New" menu item choice:
  708.  
  709.  
  710.         err = NewDocument(&frHndl, gAppWindowType, true);
  711.         if (!err) {
  712.             err = DoNewWindow(frHndl, nil, FrontWindow(), (WindowPtr)-1);
  713.             if (err)
  714.                 DisposeDocument(frHndl);
  715.         }
  716.         if (err)
  717.             CenteredAlert(rErrorAlert, nil, (ModalFilterProcPtr)AlertFilter);
  718.         break;
  719.  
  720.  
  721. We have explored what happens when NewDocument() is called.  All that remains
  722. is the DoNewWindow() call.
  723.  
  724.  
  725. A window is opened for the frHndl.  The values stored in the frHndl are used
  726. to determine how to open the window.
  727.  
  728. In the sample call above, we create a window for the document frHndl.  Here's
  729. what the parameters indicate:
  730.  
  731. nil:            Don't bother returning a reference to the window created.
  732.                 If we cared, we might pass in something like &newWindow.
  733. FrontWindow():    Window is created on the same monitor that holds most of the
  734.                 current front window.  
  735. (WindowPtr)-1:    Window is created as the front-most window.
  736.  
  737.  
  738. That's all there is to it.  Now all you need is a way to hold your document
  739. data.
  740.  
  741.  
  742. Interestingly enough, DTS.framework has plenty of document support.  You can
  743. use the hierarchical document architecture built into DTS.framework.  It is
  744. very powerful.  It handles file I/O, hierarchically related data, infinite
  745. undo, etc.
  746.  
  747.  
  748. The hierarchical document architecture is the default type of document.  If
  749. you choose to use it, there are already calls supplied to create the initial
  750. default document.  All you have to do is to call DefaultInitDocument() in
  751. your application's InitDocument() function.  For all documents that use the
  752. hierarchical document architecture, simply call DefaultInitDocument() from
  753. within InitDocument().
  754.  
  755. What DefaultInitDocument does is it creates a root object of type TreeObjHndl
  756. and places it in an expected location within the frHndl for the document.
  757. Consider this as a document within a document.  The frHndl describes the
  758. run-time aspects of the document.  The root object in the hierarchy begins
  759. the portion of the document that "persists", that is, saves to disk.
  760.  
  761. The root object is just that.  Yes, it has a data portion to it, but the main
  762. purpose of the root object is a holder of the "child" objects of the document.
  763.  
  764. Whenever you make a change to the document, you will make it to some number of
  765. objects with the hierarchy.  There are specific calls for this, and if you stick
  766. to these calls, infinite undo works automatically.  There is a separate document
  767. describing the hierarchical document architecture.  You will find it in the
  768. DTS.Lib folder.  It is called "=Using TreeObj.c".  It will detail everything
  769. you need to know about using the hierarchical document package.
  770.  
  771. Let's review the chain of information for a document, startiong with the window:
  772.  
  773. window:        References the document (frHndl) associated with the window.
  774.             The reference to the frHndl is kept in the window's refcon field.
  775.  
  776. frHndl:        References back to the window.  This means that if you have either
  777.             the window or the frHndl for a document, you can easily get the
  778.             other.
  779.  
  780.             Also references the root hierarchy object.  This is true only if you
  781.             elect to use the TreeObj package in the DTS.framework.  DTS.Draw
  782.             uses this package heavily.  Wannabe also uses it, but has no objects
  783.             defined, as Wannabe is only an application shell.
  784.  
  785.  
  786. root:        References the frHndl that contains it.
  787.  
  788.             Also references the undo root object that contains all of the
  789.             information necessary to undo/redo edits to the document.
  790.  
  791.             References all of the children added to the root object.
  792.  
  793.  
  794. child:        References all children it may in turn have.
  795.  
  796.             References the parent object.  The root object can also be
  797.             considered a child, but it is a child with no parent.  This
  798.             is reflected in the parent reference.  If the parent reference
  799.             is nil, then the object is a root object.
  800.  
  801.  
  802. This is a nice top-down view of where document/window information is stored.
  803. Here's some more information:
  804.  
  805. frHndl information is NOT saved with the document.  Any information you wish
  806. to be saved with the document must be contained in a hierarchy object.
  807.  
  808. Changes to the root object can't be undone.  The location in the document
  809. that is automatically kept for undo/redo operations is referenced by parent
  810. and child number.  This locates you to any object within the hierarchy,
  811. except for one.  The root object has no parent, so the location of the root
  812. object can not be indicated by parent/childNum.
  813.  
  814. This means:
  815.  
  816. • Any information tht is run-time, and you DON'T want saved to disk should be
  817.   kept directly in the frHndl.
  818. • Any information that you want to keep with the document, but you don't want
  819.   involved in undo/redo operations should be kept in the root object.
  820. • Any information that you want to be able to save and undo/redo in the
  821.   document should be kept in a child below the level of the root object.
  822.  
  823.  
  824. Of course, all of the rules from the root object down imply that you are using
  825. the hierarchical document architecture.  This is a completely optional package.
  826. All other rules continue to apply, whether or not you are using this package.
  827.  
  828.  
  829.  
  830.  
  831. With the exception of the root object, all of the above information is kept in
  832. the fileState portion of an frHndl.  The root object begins the actual document,
  833. so it is actually in the document portion of an frHndl.  Why am I talking about
  834. frHndl portions?  Because an frHndl is comprised of three distinct structures.
  835. They are:
  836.  
  837. FileStateRec:    This is most adequately covered above.
  838. ConnectRec:        This hasn't even been mentioned.
  839. TheDoc:            This has been hinted at.
  840.  
  841.  
  842. The ConnectRec portion of a document record looks like this:
  843.  
  844. typedef struct {
  845.     long            windowID[2];        /* Used to match up windows. */
  846.     short            endSendInfo;        /* Above is send info.         */
  847.  
  848.     Boolean            connected;            /* Flag showing we are connected.      */
  849.     AEAddressDesc    remoteLoc;            /* AppleEvent address of remote user. */
  850.     Str32            remoteName;            /* Name of user connected to.          */
  851.     Str32            remoteZone;            /* Zone of user connected to.          */
  852.     Str32            remoteMachine;        /* Machine name of user connected to. */
  853.     short            endLocalInfo;        /* Above info is for 1 machine only.  */
  854. } ConnectRec;
  855.  
  856. The ConnectRec is used by DTS.framework to establish a connection to
  857. another application (or itself).  This connection links two specific windows.
  858. Once connected, the boolean 'connected' is set true, and the other fields
  859. are filled in.  The AEAddressDesc of whom you connected to is saved.  The name,
  860. zone, and machine name are also kept.  The endLocalInfo field is a reference
  861. point to determine the end of the ConnectRec at run-time.  This is here in case
  862. there are additional fields added in the future.  They would be added before the
  863. endLocalInfo field.  Since the ConnectRec structure is DTS.framework's
  864. responsibility to maintain, you don't have to worry about this.  (I do.)
  865.  
  866.  
  867. To see how to use the Apple Event facilities of DTS.framework, check out the
  868. application Wannabe.  It uses these facilities.
  869.  
  870.  
  871.  
  872. The final component of an frHndl is the document portion.  It looks like this:
  873.  
  874.  
  875. typedef struct {
  876.     DocHeaderInfo    fhInfo;  /* Doc hdr info (vers, prRec, window loc.) */
  877.     TreeObjHndl        root;
  878. } TheDoc;
  879.  
  880.  
  881. where DocHeaderInfo looks like this:
  882.  
  883.  
  884. typedef struct {
  885.     short        version;            /* The file format version.                    */
  886.     Boolean        printRecValid;        /* True if prRec has been created.            */
  887.     TPrint        print;                /* Print record for file.                    */
  888.     Rect        structureRect;        /* Remember where window was when saved.    */
  889.     Rect        contentRect;        /* Remember where window was when saved.    */
  890.     Rect        stdState;            /* This and below rect used for saving        */
  891.     Rect        userState;            /* zoom information for window.                */
  892.     long        hDocSize;            /* hDocSize and vDocSize have to be saved    */
  893.     long        vDocSize;            /* with the document, or recalculated        */
  894.                                     /* when the file is opened so that the        */
  895.                                     /* window can be created the correct size.    */
  896.     short        endDocHeaderInfo;    /* End version, print, and window info.        */
  897. } DocHeaderInfo;
  898.  
  899.  
  900. I suppose you figured that the document portion was your domain.  Well, it is,
  901. kind of.  Since DTS.framework supplies hierarchical document support, it has
  902. to have access to this portion of the frHndl, as well.  You always need the
  903. fhInfo field of the document portion.  Also, it must be the first field in
  904. the structure.
  905.  
  906. Note the comment on the hDocSize and vDocSize fields.  You can instruct the framework
  907. to save the DocHeaderInfo structure automatically with the document, in either the
  908. data fork or the resource fork.  If you choose this option, then you don't have to
  909. worry about saving them or recalculating them.  You will still have to initialize
  910. them to something appropriate whenever you create a new document, however.
  911.  
  912. You optionally need the root field.  If you are using the hierarchical document
  913. package, then you must have this field, and it must be the second field.
  914.  
  915. Aside from these two rules, the structure is all yours.  You can add as much as
  916. you wish to it.
  917.  
  918.  
  919. As has been described above, a single frHndl consists of three components.
  920. These are:
  921.     1) FileStateRec
  922.     2) ConnectRec
  923.     3) The document record.
  924.  
  925. The way you assemble these parts into a single frHndl is like this:
  926.  
  927.  
  928. typedef struct FileRec {
  929.     FileStateRec    fileState;        /* DTS.Lib expects this structure here. */
  930.     ConnectRec        connect;        /* DTS.Lib expects this structure here. */
  931.     union {
  932.         TheDoc    doc;                /* Union in each document type here. */
  933.     } d;
  934. } FileRec;
  935.  
  936.  
  937. This combines the three documents parts into a single structure.  Note the use
  938. of union for the actual document portion.  This is because you can have multiple
  939. document types within your application.  Whenever you add a document type, just
  940. create a structure for it, and then union in this new structure into the FileRec
  941. definition.  That's all there is to it (sort of).
  942.  
  943. You have to remember to change the InitDocumentSize() and InitDocument()
  944. functions so that they can handle the new document type.  Once this is done,
  945. you now have a new document type added to your application.
  946.  
  947.  
  948. I think this should give you a good feel of what you need to first, second, etc.
  949. Any other questions about using DTS.Lib and Wannabe should be answered in other
  950. read.me files.  Of course, you can always look at the sample applications
  951. DTS.Draw and Wannabe for implementation-specific details.
  952.  
  953.  
  954.  
  955. NOTE!!    The above is very good information as to how Wannabe and the application framework
  956.         actually function.  However, a programming utility has been written that automates
  957.         the above.  This utility is called AppsToGo.  Use it.  The heck with code.
  958.         AppsToGo allows you to use Wannabe and the framework without having to code for
  959.         a while.  Eventually you will have prototyped all the functionality possible and
  960.         you will have to write some lines of code.  However, AppsToGo allows you to
  961.         defer coding for quite some time.
  962.         The changes to Wannabe that you make with AppsToGo are permanent, and adding code
  963.         to Wannabe doesn't prevent you form continuing to use AppsToGo.  CHECK IT OUT!!
  964.  
  965.  
  966. Eric Soldan
  967.